﻿// -----------------------------------------------------------------------
// <copyright file="AmazonSigningMessageInspector.cs" company="EPAM Systems">
////Copyright (c) EPAM Systems, 2012. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace MBOP.DistributedServices.AmazonHeaders
{
	#region Usings

	using System;
	using System.Security.Cryptography;
	using System.ServiceModel;
	using System.ServiceModel.Channels;
	using System.ServiceModel.Dispatcher;
	using System.Text;
	using System.Text.RegularExpressions;

	#endregion

	/// <summary>
	/// The <see cref="IClientMessageInspector"/> implementer, which represents the inspector of signing messages for amazon.
	/// </summary>
	public class AmazonSigningMessageInspector : IClientMessageInspector
	{
		#region Constants

		/// <summary>
		/// Represents the regular expression for header action.
		/// </summary>
		private const string REG_EXP_HEADER_ACTION = "[^/]+$";

		/// <summary>
		/// Represents the format string for timestamp.
		/// </summary>
		private const string FORMAT_STRING_TIMESTAMP = "yyyy-MM-ddTHH:mm:ssZ";

		/// <summary>
		/// Represents the header name for access key id.
		/// </summary>
		private const string HEADER_NAME_ACCESS_KEY_ID = "AWSAccessKeyId";
		
		/// <summary>
		/// Represents the header name for timestamp.
		/// </summary>
		private const string HEADER_NAME_TIMESTAMP = "Timestamp";

		/// <summary>
		/// Represents the header name for signature.
		/// </summary>
		private const string HEADER_NAME_SIGNATURE = "Signature";

		#endregion

		#region Private Fields

		/// <summary>
		/// Represents the access key id.
		/// </summary>
		private string _accessKeyId = string.Empty;

		/// <summary>
		/// Represents the secret key.
		/// </summary>
		private string _secretKey = string.Empty;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="AmazonSigningMessageInspector"/> class.
		/// </summary>
		/// <param name="accessKeyId">The access key unique number.</param>
		/// <param name="secretKey">The secret key.</param>
		public AmazonSigningMessageInspector(string accessKeyId, string secretKey)
		{
			this._accessKeyId = accessKeyId;
			this._secretKey = secretKey;
		}

		#endregion

		#region IClientMessageInspector implementation

		/// <summary>
		/// Enables inspection or modification of a message before a request message is sent to a service.
		/// </summary>
		/// <param name="request">The message to be sent to the service.</param>
		/// <param name="channel">The WCF client object channel.</param>
		/// <returns>The object that is returned as the correlationState argument of the AfterReceiveReply method.</returns>
		public object BeforeSendRequest(ref Message request, IClientChannel channel)
		{
			string operation = Regex.Match(request.Headers.Action, REG_EXP_HEADER_ACTION).ToString();
			DateTime now = DateTime.UtcNow;
			string timestamp = now.ToString(FORMAT_STRING_TIMESTAMP);
			string signMe = operation + timestamp;
			byte[] bytesToSign = Encoding.UTF8.GetBytes(signMe);

			byte[] secretKeyBytes = Encoding.UTF8.GetBytes(this._secretKey);
			HMAC hmacSha256 = new HMACSHA256(secretKeyBytes);
			byte[] hashBytes = hmacSha256.ComputeHash(bytesToSign);
			string signature = Convert.ToBase64String(hashBytes);

			request.Headers.Add(new AmazonHeader(HEADER_NAME_ACCESS_KEY_ID, this._accessKeyId));
			request.Headers.Add(new AmazonHeader(HEADER_NAME_TIMESTAMP, timestamp));
			request.Headers.Add(new AmazonHeader(HEADER_NAME_SIGNATURE, signature));

			return null;
		}

		/// <summary>
		/// Enables inspection or modification of a message after a reply message is received but prior to passing it back to the client application.
		/// </summary>
		/// <param name="reply">The message to be transformed into types and handed back to the client application.</param>
		/// <param name="correlationState">Correlation state data.</param>
		public void AfterReceiveReply(ref Message reply, object correlationState) 
		{
		}

		#endregion
	}
}
